package edu.lnu.ums.security;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

import org.hibernate.HibernateException;
import org.hibernate.classic.Session;

import edu.lnu.ums.dal.IUserDAO;
import edu.lnu.ums.dal.impl.UserDAO;
import edu.lnu.ums.entities.security.UserGroup;
import edu.lnu.ums.entities.security.UserPrincipal;
import edu.lnu.ums.entities.security.User;
import edu.lnu.ums.entities.security.UserType;

public class LoginModuleImpl implements LoginModule {
	// initial state
	CallbackHandler handler;
	Subject subject;
	Map sharedState;
	Map options;

	private UserPrincipal userPrincipal;
	private UserPrincipal[] roles;

	// authentication status
	boolean success;

	// configurable options
	boolean debug;

	private IUserDAO userDAO = null;
	User user;

	public LoginModuleImpl() {

		success = false;
		debug = false;
		user = new User();
		try {
			userDAO = (IUserDAO) InitialContext.doLookup("UMS/UserDAO/local");
		} catch (NamingException e) {
			e.printStackTrace();
		}
	}

	/*
	 *** Initialize our state.
	 */
	public void initialize(Subject subject, CallbackHandler handler,
			Map sharedState, Map options) {

		this.handler = handler;
		this.subject = subject;
		this.sharedState = sharedState;
		this.options = options;

		if (options.containsKey("debug")) {
			debug = "true".equalsIgnoreCase((String) options.get("debug"));
		}

		// elided: standard code to get Hibernate =SessionFactory=.
	}

	/**
	 * First phase of login process.
	 */
	public boolean login() throws LoginException {
		if (handler == null) {
			throw new LoginException("Error: no CallbackHandler available");
		}

		try {
			Callback[] callbacks = new Callback[] { new NameCallback("User: "),
					new PasswordCallback("Password: ", false) };

			handler.handle(callbacks);

			String username = ((NameCallback) callbacks[0]).getName();
			char[] password = ((PasswordCallback) callbacks[1]).getPassword();

			((PasswordCallback) callbacks[1]).clearPassword();

			success = validate(username, password);
			userPrincipal = new UserPrincipal(user.getName());
			roles = new UserPrincipal[] { new UserPrincipal("ADMIN"),
					new UserPrincipal("USER") };

			callbacks[0] = null;
			callbacks[1] = null;
			if (!success) {
				throw new LoginException(
						"Authentication failed: Password does not match");
			}
			return true;
		} catch (LoginException e) {
			throw e;
		} catch (Exception e) {
                        e.printStackTrace();
			success = false;
			throw new LoginException(e.getMessage());
		}

	}

	/**
	 * Second phase of login - by now we know user is authenticated and we just
	 * need to update the subject.
	 */
	public boolean commit() throws LoginException {
		if (success) {
			if (subject.isReadOnly()) {
				throw new LoginException("Subject is read-only");
			}

			try {

				subject.getPrincipals().add(userPrincipal);
				UserGroup group = new UserGroup("Roles");
				for (UserPrincipal role : roles) {
					if (user.getType().equals(role.getName())) {
						group.addMember(role);
					}
				}
				subject.getPrincipals().add(group);

				return true;
			} catch (Exception e) {
				throw new LoginException(e.getMessage());
			}
		} else {

		}
		return true;
	}

	/**
	 * Second phase - somebody else rejected user so we need to clear our state.
	 */
	public boolean abort() throws LoginException {
		success = false;
		logout();
		return true;
	}

	/**
	 * User is logging out - clear our information from the subject.
	 */
	public boolean logout() throws LoginException {

		// remove the principals the login module added
		Iterator i = subject.getPrincipals(User.class).iterator();
		while (i.hasNext()) {
			User p = (User) i.next();
			subject.getPrincipals().remove(p);
		}

		return true;
	}

	/*
	  Validate the user name and password. This is the Hibernate-specific code.
	 */

	private boolean validate(String username, char[] password) throws Exception {
		boolean valid = false;
		user = userDAO.getByName(username);
                

		String hash = user.getPassword();
		if (hash != null && password != null && password.length > 0) {
			if (hash.equals(new String(password)) && user.getActive()) {
				valid = true;
			}
		}

		return valid;
	}
}